package com.spring.nio.service.file.util;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.spring.nio.business.user.dto.UserDTO;
import com.spring.nio.business.user.param.UserQueryParam;
import com.spring.nio.business.user.service.UserService;
import com.spring.nio.common.BasicConstant;
import com.spring.nio.core.channel.context.ChannelContext;
import com.spring.nio.core.channel.context.SimpleChannelContext;
import com.spring.nio.core.channel.context.SocketChannelContext;
import com.spring.nio.core.configuration.NioServerBootstrap;
import com.spring.nio.service.file.frame.FileMessageFrame;
import com.spring.nio.service.file.handler.FileUploadTransportStreamHandler;
import com.spring.nio.service.file.model.FileStreamBasicData;
import com.spring.nio.util.ByteOrderConvert;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.UnsupportedEncodingException;
import java.util.*;

/**
 * 文件帧解析工具
 * @author spring
 * */
@Slf4j
public class FileFrameSpecificParse {
    private static final FileFrameSpecificParse parse = new FileFrameSpecificParse();
    private static UserService userService = null;
    static {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = NioServerBootstrap.getConfig().getClassPathXmlApplicationContext();
        userService = classPathXmlApplicationContext.getBean(UserService.class);
    }

    private FileFrameSpecificParse() {}

    /**
     * 由于加锁，当前handler同一时刻只能一个线程在运行, 记录当前正在处理的用户DTO
     * */
    private static UserDTO currentUserDto = null;

    /**
     * 获取文件帧解析实例对象
     * */
    public static final FileFrameSpecificParse getParse() {
        return parse;
    }

    /**
     * 基础文件帧解析: 解析帧类型、文件操作类型、文件类型
     * @param frameTypeByte  帧类型字节
     * @param fileTypeByte   文件类型字节
     * @param fileOperateByte 文件操作类型字节
     * @param fileMessageFrame 当前文件帧
     */
    public void executeBasicFileFrameParse(byte frameTypeByte, byte fileTypeByte, byte fileOperateByte, FileMessageFrame fileMessageFrame) {
        StringBuilder stringBuilder = new StringBuilder("");

        // 1、解析帧类型
        stringBuilder.append(Integer.toBinaryString(frameTypeByte & 0xff));
        while (stringBuilder.toString().length() != 8) {
            stringBuilder.insert(0, "0");
        }
        Arrays.asList(FileMessageFrame.FrameType.values())
            .stream()
            .filter(predict -> (StringUtils.equals(predict.getBit(), stringBuilder.toString())))
            .forEach(frameType -> {
                fileMessageFrame.setFrameType(frameType);
            });

        // 2、解析并设置文件类型
        stringBuilder.delete(0, stringBuilder.length());
        stringBuilder.append(Integer.toBinaryString(fileTypeByte & 0xff));
        while (stringBuilder.toString().length() != 8) {
            stringBuilder.insert(0, "0");
        }
        Arrays.asList(FileMessageFrame.FileType.values())
            .stream()
            .filter(predict -> (StringUtils.equals(predict.getBit(), stringBuilder.toString())))
            .forEach(fileType -> {
                fileMessageFrame.setFileType(fileType);
            });

        // 3、解析并设置文件操作类型
        stringBuilder.delete(0, stringBuilder.length());
        stringBuilder.append(Integer.toBinaryString(fileOperateByte & 0xff));
        while (stringBuilder.toString().length() != 8) {
            stringBuilder.insert(0, "0");
        }
        Arrays.asList(FileMessageFrame.FileOperateType.values())
            .stream()
            .filter(predict -> (StringUtils.equals(predict.getBit(), stringBuilder.toString())))
            .forEach(fileOperateType -> {
                fileMessageFrame.setFileOperateType(fileOperateType);
            });
    }

    /**
     * 解析上传、下载和在线传输帧中的文件名称、文件大小
     * @param i 解析起始索引位置
     * @param sumBytes
     * @param fileMessageFrame
     * @return index 返回文件基本数据解析后的字节索引，即读取真实数据开始的索引位置
     */
    public int executeDynamicFileFrameParse(Integer i, byte[] sumBytes, FileMessageFrame fileMessageFrame) {

        // 1、解析文件名称(上传、下载和在线传输)长度
        byte[] shortBytes = new byte[2];
        shortBytes[0] = sumBytes[i + 8];
        shortBytes[1] = sumBytes[i + 9];
        fileMessageFrame.setDataLength(ByteOrderConvert.bytesToShort(shortBytes));

        // 2、解析文件总大小 8B
        byte[] fileLengthBytes = new byte[8];
        int k = 0, current = i + 10;
        do {
            fileLengthBytes[k] = sumBytes[current];
            current += 1;
        } while (++k < 8);
        fileMessageFrame.setFileLength(ByteOrderConvert.bytesToLong(fileLengthBytes));
        return (i + FileMessageFrame.UPLOAD_COMMON);
    }

    /**
     * 解析文件流帧中固定数据
     * @param i
     * @param sumBytes
     * @param fileMessageFrame
     */
    public void parseFileStreamFrameBasicData(Integer i, byte[] sumBytes, FileMessageFrame fileMessageFrame) {
        // 1、读取两个字节获取接收端用户名或文件名称长度数据到dataLength
        byte[] shortBytes = new byte[2];
        shortBytes[0] = sumBytes[i + 8];
        shortBytes[1] = sumBytes[i + 9];
        // 文件流帧总长度
        fileMessageFrame.setDataLength(ByteOrderConvert.bytesToShort(shortBytes));

        int k = i + 10;
        if(fileMessageFrame.getDataLength() > 0) {
            byte[] receiveBytes = new byte[fileMessageFrame.getDataLength()];
            for(int j = 0; j < fileMessageFrame.getDataLength(); j++) {
                receiveBytes[j] = sumBytes[k];
                k++;
            }

            try {
                fileMessageFrame.setData(new String(receiveBytes, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                fileMessageFrame.setData("解析文件流帧中的基本数据异常，error = " + e.getMessage());
                e.printStackTrace();
            }
        }
    }

    /**
     * 文件上传实时流解析
     * @param i
     * @param sumBytes
     * @param sumBytes
     * @param fileMessageFrame
     */
    public void executeUploadStreamDataTransport(int i, byte[] sumBytes, FileMessageFrame fileMessageFrame, ChannelContext channelContext) {
        // 1、解析帧
        parseFileStreamFrameBasicData(i, sumBytes, fileMessageFrame);

        // 2、拷贝当前帧中的文件流数据 其中 10 包含 ： 3个字节的(帧类型，文件类型，操作类型数据)， 2个字节的数据内容长度字节(即short), 4个字节的包序号 1个字节的是否为结束帧
        byte[] fileStreamBytes = new byte[sumBytes.length - (10 + fileMessageFrame.getDataLength())];
        System.arraycopy(sumBytes, (10 + fileMessageFrame.getDataLength()), fileStreamBytes, 0, fileStreamBytes.length);

        // 3、根据fileTaskTag获取当前用户文件上传任务,封装文件上传当前片段任务streamMap，并跳转至文件流传输handler向接收端用户发送文件流数据文件流数据
        Map<String, Object> streamMap = new HashMap<>();
        streamMap.put(BasicConstant.ORIGIN_FILE_STREAM_DATA, fileStreamBytes);
        streamMap.put(BasicConstant.FILE_FRAME, fileMessageFrame);
        FileStreamBasicData fileStreamBasicData = JSONObject.parseObject(fileMessageFrame.getData(), FileStreamBasicData.class);
        if(StringUtils.equals("NOT_LOGIN", fileStreamBasicData.getIsLogin())) {
            // 用户未登录，使用临时用户上传文件集合获取文件上传任务
            streamMap.put(BasicConstant.FILE_TASK, FileTransportManager.temporaryTransportMap.get(fileStreamBasicData.getFileTransportAddress()));
        } else {
            UserQueryParam userQueryParam = new UserQueryParam();
            userQueryParam.setUserName(fileStreamBasicData.getUserName());
            currentUserDto = userService.getOnlineUserByName(userQueryParam);
            streamMap.put(BasicConstant.FILE_TASK, currentUserDto.getUploadFileMap().get(fileStreamBasicData.getFileTransportAddress()));
        }

        // 5、--> 优化点: 由于FileUploadTask 不在进行公平锁的介入，此处存在线程安全问题(多个线程会持有同一socketChannelContext的DefaultChannelPipeLine,那么都会进入同一个channelHandler),
        // 此处线程不安全点: 线程1 可处理的数据包序号[1 ~ 10], 线程2 可处理包序号 [11 - 20]，有可能存在线程2的数据包先添加至list，线程1的数据包后添加至list，导致写入顺序出现问题,在后期需要排序后写入
        // 当前channelContext为 FileFrameDecodeHandler对应的ChannelContext
        SimpleChannelContext simpleChannelContext = (SimpleChannelContext) channelContext;
        // --> 跳转至FileOnlineTransportStreamHandler
        simpleChannelContext.setNeedSkip(Boolean.TRUE).setSkip(3);
        synchronized (FileUploadTransportStreamHandler.class) { // 已上传为主，此处使用上传处理FileUploadTransportStreamHandler进行
            if(!Objects.isNull(simpleChannelContext.getObj())) {
                List<Map<String, Object>> streamList = Lists.newArrayList();
                streamList.add(streamMap);
                simpleChannelContext.setObj(streamList);
            } else {
                ((List<Map<String, Object>>) simpleChannelContext.getObj()).add(streamMap);
            }
        }
    }

    /**
     * 文件上传结束消息解析
     * @param i
     * @param sumBytes
     * @param fileMessageFrame
     * @param socketChannelContext
     */
    public void executeDataTransportEndFrameParse(Integer i, byte[] sumBytes, FileMessageFrame fileMessageFrame, SocketChannelContext socketChannelContext) {
        // 解析文件流传输结束帧中的数据内容长度(即需要持久化文件上传成功的记录数据)
        parseFileStreamFrameBasicData(i, sumBytes, fileMessageFrame);
        // 直接将该帧加入当前通道上下文真实数据List
        socketChannelContext.getRealList().add(fileMessageFrame);
    }

    /**
     * 文件在线传输实时流解析
     * @param i
     * @param sumBytes
     * @param sumBytes
     * @param fileMessageFrame
     */
    public void executeOnlineTransportStreamDataTransport(Integer i, byte[] sumBytes, FileMessageFrame fileMessageFrame, ChannelContext channelContext) {
        // 1、解析帧
        parseFileStreamFrameBasicData(i, sumBytes, fileMessageFrame);

        // 2、拷贝当前帧中的文件流数据 其中 10 包含 ： 3个字节的(帧类型，文件类型，操作类型数据)， 2个字节的数据内容长度字节(即short), 4个字节的包序号 1个字节的是否为结束帧
        byte[] fileStreamBytes = new byte[sumBytes.length - (10 + fileMessageFrame.getDataLength())];
        System.arraycopy(sumBytes, (10 + fileMessageFrame.getDataLength()), fileStreamBytes, 0, fileStreamBytes.length);

        // 3、根据fileTaskTag获取当前用户文件上传任务,封装文件上传当前片段任务streamMap，并跳转至文件流传输handler向接收端用户发送文件流数据文件流数据
        Map<String, Object> streamMap = new HashMap<>();
        streamMap.put(BasicConstant.ORIGIN_FILE_STREAM_DATA, fileStreamBytes);
        streamMap.put(BasicConstant.FILE_FRAME, fileMessageFrame);
        // 4、向下游传递文件流处理
        SimpleChannelContext simpleChannelContext = (SimpleChannelContext) channelContext;
        if(!Optional.ofNullable(simpleChannelContext.getObj()).isPresent()) {
            List<Map<String, Object>> list = Lists.newArrayList();
            list.add(streamMap);
            simpleChannelContext.setObj(list);
        } else {
            ((List<Map<String, Object>>) simpleChannelContext.getObj()).add(streamMap);
        }

        // 直接跳转到FileReceiveHandler进行处理
        ((SimpleChannelContext) channelContext).setNeedSkip(Boolean.TRUE).setSkip(1);
    }
}
